#include "movreader.h"
#include "movinteral.h"
#include "mpeg4avc.h"
#include "movfilebuffer.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

const struct mov_buffer_t* mov_file_buffer(void);

#define H264_NAL(v)	(v & 0x1F)

static uint8_t s_buffer[512 * 1024];  // 512K 存一帧数据
static uint8_t s_extra_data[64 * 1024];    // 64k

struct mov_h264_test_t
{
	mov_writer_t* mov;      // MP4
	struct mpeg4_avc_t avc;    // AVC

	int track;
	int width;
	int height;
	uint32_t pts, dts;    // 时间戳；
	const uint8_t* ptr;      // 记录264 文件数据
    int vcl;                 // 记录VCL的NAL个数；当遇到编码参数的描述NAL的时候，就会清零
};

static uint8_t* file_read(const char* file, long* size)
{
	FILE* fp = fopen(file, "rb");
	if (fp)
	{
		fseek(fp, 0, SEEK_END);    // 先去文件尾；得到文件的总大小
		*size = ftell(fp);         // 记录文件总大小
		fseek(fp, 0, SEEK_SET);
		printf("264 file len is : %d \n",*size);
		uint8_t* ptr = (uint8_t*)malloc(*size);
		fread(ptr, 1, *size, fp);
		fclose(fp);

		return ptr;      // 读取文件的全部内容
	}

	return NULL;
}
/*
 @function : 按照传进来的NAL，进行解析封装到MP4中
*/
static int h264_write(struct mov_h264_test_t* ctx, const void* data, int bytes)
{
    int vcl = 0;
    int update = 0;
	/* 将一帧数据的元数据封装到对应的 ctx->avc 中，同时将视频数据存入到 s_buffer 中*/
    int n = h264_annexbtomp4(&ctx->avc, data, bytes, s_buffer, sizeof(s_buffer), &vcl, &update);

    if (ctx->track < 0)    // 判断是不是创建过track BOX 
    {
        if (ctx->avc.nb_sps < 1 || ctx->avc.nb_pps < 1)    // 是否记录过SPS  PPS 参数
        {
            //  ctx->ptr = end;
            return -2; // 等待 sps/pps
        }
		// 获取AVC的解码器配置参数保存到 extra_data 
        int extra_data_size = mpeg4_avc_decoder_configuration_record_save(&ctx->avc, s_extra_data, sizeof(s_extra_data));
        if (extra_data_size <= 0) 
        {
            // invalid AVCC
            assert(0);
            return -1;
        }

        // 创建track，然后将AVC中的解码器配置参数(sps pps profile level ... 等) 写入到 mov 的 mov_sample_entry_t 中
        ctx->track = mov_writer_add_video(ctx->mov, MOV_OBJECT_H264, ctx->width, ctx->height, s_extra_data, extra_data_size);
        if (ctx->track < 0)
            return -1;
    }
	// 写入一帧的数据到MP4中
	mov_writer_write(ctx->mov, ctx->track, s_buffer, n, ctx->pts, ctx->pts, 1 == vcl ? MOV_AV_FLAG_KEYFREAME : 0);
    ctx->pts += 40;    // 40ms  25 fps
    ctx->dts += 40;
    return 0;
}
/*
 @param：ctx结构体对象，就是要封装的那个 MP4 结构体
 @nalu ： 每个 nal 单元中的数据起始地址
 @byte : 当前nal 中的数据大小
 @264NALU格式 ：  | SPS | PPS | SEI | VCL | VCL | VCL | ... ... | SPS | PPS | SEI | VCL | VCL | VCL | 
*/
//  ----   264  to  mp4 
static void h264_handler(void* param, const uint8_t* nalu, size_t bytes)
{
	struct mov_h264_test_t* ctx = (struct mov_h264_test_t*)param;
	assert(ctx->ptr < nalu); 
    const uint8_t* ptr = nalu - 3;  /* 带上起始码的 nalu 地址*/
//	const uint8_t* end = (const uint8_t*)nalu + bytes;
	/* 检查这个 NALU 的数据类型 */
	uint8_t nalutype = nalu[0] & 0x1f;    //nalu[0]  = 1 bit（是否在网络上传输---一般为0）  +  2 bit（重要性，值越大，越重要）  +  5 bit（EBSP数据结构类型）
    /* 一个这样的块(一帧) ，才会写入一次 ====>  | SPS | PPS | SEI | VCL | VCL | VCL | */
	/* 当遇到下一帧的起始 NALU 的 | SPS | 之后，就会进入 if 里面，将前一个 NALU 的数据写入到 MP4 中*/
	if (ctx->vcl > 0 && h264_is_new_access_unit((const uint8_t*)nalu, bytes))    // 每次遇到新帧的 NAL 或则元数据之后，才去写
    {
		/* 写入一帧新的数据  ctx->ptr 数据的起始地址 ptr - ctx->ptr 大小 */
		// 将这一帧的数据中的元数据封装到对应的box中，将视频数据封装到缓存区中
        int r = h264_write(ctx, ctx->ptr, ptr - ctx->ptr);    // 对新帧的NAL或non-VCL进行写入
        if (-1 == r)
			return; 
            
        ctx->ptr = ptr;   // 偏移到下一个 sps 的 nal 中
        ctx->vcl = 0; 
    }
    // 判断当前的NAL是否为VCL
	if (1 <= nalutype && nalutype <= 5)
        ++ctx->vcl;  /* 记录VCL个数 */
}

// 264 转 MP4封装函数
void mov_writer_h264(const char* h264, int width, int height, const char* mp4)
{
	struct mov_h264_test_t ctx;     // 264 结构体
	memset(&ctx, 0, sizeof(ctx));
	ctx.track = -1;
	ctx.width = width;    /* 1920 */
	ctx.height = height;  /* 1080 */

	long bytes = 0;
	uint8_t* ptr = file_read(h264, &bytes);   // 将数据读取到ptr指向的地址；同时获取文件的大小记录在bytes
	
	if (NULL == ptr) return;
	ctx.ptr = ptr;                            // 数据给到结构体
	/* 创建MP4文件*/
	FILE* fp = fopen(mp4, "wb+");
	ctx.mov = mov_writer_create(mov_file_buffer(), fp, MOV_FLAG_FASTSTART);    /* 创建 MP4 外壳 */
	mpeg4_h264_annexb_nalu(ptr, bytes, h264_handler, &ctx);   // 裸流数据；数据大小；回调；MP4结构体对象
	mov_writer_destroy(ctx.mov);    // 连接mov和avc的结构体；根据视屏文件中解析出来的东西；进行封装MP4

	fclose(fp);
	free(ptr);
}
